package ci.web.core;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashSet;
import java.util.List;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONAware;

import ci.web.CiSystemConfig;
import ci.web.router.CiFile;
import ci.web.util.MimeUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.DefaultCookie;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.ServerCookieEncoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedFile;

class CiResponseImp implements CiResponse {

    protected ChannelHandlerContext context;
    protected FullHttpRequest request;
    protected DefaultHttpResponse response;
    private long contentLength = 0;
    private ByteBuf body = null;
    private Object fileInfo = null;
    protected HashSet<Cookie> cookies = null;
    
    public CiResponseImp(ChannelHandlerContext ctx, FullHttpRequest req) {
        this.request = req;
        this.context = ctx;
        this.response = new DefaultHttpResponse(req.protocolVersion(), HttpResponseStatus.OK);
        setHeader(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
    }

    @Override
    public void setStatus(HttpResponseStatus status) {
        response.setStatus(status);
    }
    @Override
    public void setStatus(int statusCode, CharSequence statusInfo) {
        response.setStatus(new HttpResponseStatus(statusCode, String.valueOf(statusInfo)));
    }

    @Override
    public void setContentType(CharSequence contentType) {
        setHeader(HttpHeaderNames.CONTENT_TYPE, contentType);
    }

    @Override
    public void setHeader(CharSequence header, CharSequence value) {
        response.headers().set(header, value);
    }

    @Override
    public void setCookie(Cookie cookie) {
        if(cookies==null){
            cookies = new HashSet<Cookie>();
        }
        cookies.add(cookie);
    }
    @Override
    public void setCookie(String name, String value) {
        setCookie(name, value, Long.MIN_VALUE);
    }
    @Override
    public void setCookie(String name, String value, long maxAge) {
        setCookie(name, value, maxAge, "/", null, false, true);
    }
    @Override
    public void setCookie(String name, String value, long maxAge, String path, String domain, boolean secure, boolean httpOnly) {
        Cookie c = new DefaultCookie(name, value);
        c.setMaxAge(maxAge);
        c.setDomain(domain);
        c.setHttpOnly(httpOnly);
        c.setSecure(secure);
        c.setPath(path);
        setCookie(c);
    }
    @Override
    public void redirect(CharSequence location) {
        response.setStatus(HttpResponseStatus.MOVED_PERMANENTLY);
        setHeader(HttpHeaderNames.LOCATION, location);
        HttpHeaderUtil.setKeepAlive(response, false);
    }

    @Override
    public void redirect(CharSequence location, int httpStatusCode) {
        response.setStatus(new HttpResponseStatus(httpStatusCode, "rediret"));
        setHeader(HttpHeaderNames.LOCATION, location);
        HttpHeaderUtil.setKeepAlive(response, false);
    }

    @Override
    public void sendError(CharSequence errorInfo) {
        sendError(500, errorInfo);
    }

    @Override
    public void sendError(int code, CharSequence errorInfo) {
        setStatus(code, errorInfo);
        HttpHeaderUtil.setKeepAlive(response, false);
    }

    @Override
    public void send(File file){
        this.send(file, 604800);
    }
    @Override
    public void send(File file, int fileCacheSecond) {
        try{
            long fileLength = file.length();
            contentLength += fileLength;
            
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            
            setContentType(MimeUtil.getMimeType(file));
            CiFile.sendDateAndCache(this, file, fileCacheSecond);
            
            if(context.pipeline().get(SslHandler.class)==null){
                fileInfo = new DefaultFileRegion(raf.getChannel(), 0L, fileLength);
            }else{
                fileInfo = new ChunkedFile(raf, 0L, fileLength, 8192);
            }
        }catch(IOException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void send(JSONAware info) {
        setHeader(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
        sendBytes(JSON.toJSONBytes(info));
    }
    @Override
    public void send(CharSequence body) {
        sendBytes(body.toString().getBytes());
    }
    @Override
    public void send(byte[] body) {
        setHeader(HttpHeaderNames.CONTENT_TYPE, "application/octet-stream");
        sendBytes(body);
    }
    protected void sendBytes(byte[] bin) {
        contentLength += bin.length;
        if(body==null){
            body = Unpooled.buffer(bin.length);
        }
        body.writeBytes(bin);
    }

    @Override
    public boolean isWroteBody(){
    	return body!=null;
    }
    @Override
    public boolean isFinish(){
        return response==null;
    }
    @Override
    public synchronized void finish(){
        if(response==null){
            return;
        }
        HttpHeaders headers = response.headers();
        if(cookies!=null){
            List<String> arr = ServerCookieEncoder.encode(cookies);
            for(String s:arr){
                headers.add(HttpHeaderNames.SET_COOKIE, s);
            }
        }
        if(headers.contains(HttpHeaderNames.CONTENT_TYPE)){
            String ctype = headers.get(HttpHeaderNames.CONTENT_TYPE).toString();
            if(ctype.indexOf("charset")<0){
                ctype = ctype+"; charset=UTF-8";
                headers.set(HttpHeaderNames.CONTENT_TYPE, ctype);
            }
        }
        HttpHeaderUtil.setContentLength(response, contentLength);
        boolean close = true;
        if(CiSystemConfig.HttpKeepAlive && HttpHeaderUtil.isKeepAlive(request)){
            close = false;
            HttpHeaderUtil.setKeepAlive(response, true);
        }
        context.write(response);
        if(body!=null){
            if(body.isReadable()){
                context.write(body);
            }else{
                body.release();
            }
        }
        if(fileInfo!=null){
            context.write(fileInfo, context.newProgressivePromise());
        }
        if(close){
            context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE);
        }else{
            context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        }
        response = null;
    }
    
    @Override
    public synchronized void cleanFinish(){
    	response=null;
    }
    
    @Override
    public ChannelHandlerContext ioContext(){
    	return context;
    }
}
